﻿using HidApi;
using Microsoft.VisualBasic.FileIO;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.Intrinsics.Arm;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using static System.Formats.Asn1.AsnWriter;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace MySoftLab.DapFlashCore.CoreSight
{
    /// <summary>
    /// Component discovery process for ADIv5.
    /// Component discovery for ADIv5 proceeds as follows.Each of the steps is labeled with the name
    /// of the init task for that step.
    ///
    /// 1. `find_aps`: Perform an AP scan. Probe each AP at APSEL= 0..255.By default the scan stops on
    /// the first invalid APSEL, as determined by testing the IDR value (0 is invalid). This can be
    /// overridden by a session option.
    /// 2. `create_aps`: Create all APs and add them to the DP.
    /// 3. `find_components`: For each AP, read the associated ROM table(s) and identify CoreSight
    ///    components.
    /// 4. `create_cores`: Create any discovered core (CPU) components. The cores are created first to
    /// ensure that other components have a core to which they may be connected.
    /// 5. `create_components`: Create remaining discovered components.
    /// </summary>
    public class Discovery
    {
        private DebugPort _dp;

        // ## APSEL is 8-bit, thus there are a maximum of 256 APs.
        public const int MAX_APSEL = 255;

        public Discovery(DebugPort dp)
        {
            _dp = dp;
        }

        public void Discover()
        {
            FindAps();
            var actions = CreateAps();
            foreach (var action in actions)
            {
                action();
            }

            actions = FindComponents();
            foreach (var action in actions)
            {
                action();
            }

            CreateCores();
            CreateComponents();
        }

        /// <summary>
        /// Find valid APs using the ADIv5 method.

        /// Scans for valid APs starting at APSEL=0. The default behaviour is to stop after reading
        /// 0 for the AP's IDR twice in succession. If the `scan_all_aps` session option is set to True,
        /// then the scan will instead probe every APSEL from 0-255.

        /// If there is already a list of valid APs defined for the @ref pyocd.coresight.dap.DebugPort
        /// DebugPort(the `valid_aps` attribute), then scanning is not performed.This is to allow a
        /// predetermined list of valid APSELs to be used in place of a scan. A few MCUs will lock up
        /// when accessing invalid APs.On those devices, scanning with the method used here cannot be
        /// done.
        /// </summary>
        private Action[] FindAps()
        {
            if (_dp.ValidAps.Count != 0)
            {
                return [];
            }

            List<APAddressBase> apList = new List<APAddressBase>();

            int invalidCount = 0;
            for (uint i = 0; i < MAX_APSEL; i++)
            {
                try
                {
                    var isValid = AccessPort.Probe(_dp, i);
                    if (isValid)
                    {
                        apList.Add(new APv1Address(i));
                        invalidCount = 0;
                    }
                    else
                    {
                        invalidCount++;
                    }
                }
                catch (Exception)
                {
                    Console.WriteLine($"error probe AP=#{i}");
                    invalidCount++;
                }

                // # Stop scanning if we've seen a maximum number of invalid APs, unless `scan_all_aps` is set.
                // TODO
                if (invalidCount >= 3)
                {
                    break;
                }
            }

            // Update the AP list once we know it's complete.
            _dp.ValidAps = apList;
            return [];
        }

        /// <summary>
        /// Init task that returns a call sequence to create APs.
        /// For each AP in the #valid_aps list, an AccessPort object is created. The new objects
        /// are added to the #aps dict, keyed by their AP number.
        /// </summary>
        private Action[] CreateAps()
        {
            List<Action> actions = new List<Action>();

            foreach (var apSel in _dp.ValidAps)
            {
                actions.Add(() => CreateOneAP(apSel));
            }

            return actions.ToArray();
        }

        /// <summary>
        /// Init task to create a single AP object
        /// </summary>
        /// <param name="apSel"></param>
        private void CreateOneAP(APAddressBase apSel)
        {
            try
            {
                var ap = AccessPort.Create(_dp, apSel);
                if (ap != null)
                {
                    var csw = ap.ReadReg(AccessPort.MEM_AP_CSW).Value;
                    Console.WriteLine($"csw = 0x{csw:x8}");
                    if (_dp.Aps.ContainsKey(apSel))
                    {
                        _dp.Aps[apSel] = ap;
                    }
                    else
                    {
                        _dp.Aps.Add(apSel, ap);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error reading AP#{apSel.NominalAddress} IDR: {ex}");
            }
        }

        /// <summary>
        /// Init task that generates a call sequence to ask each AP to find its components
        /// </summary>
        private Action[] FindComponents()
        {
            List<Action> actions = [];

            foreach (var ap in _dp.Aps.Values)
            {
                if (ap.HasRomTable)
                {
                    actions.Add(ap.FindComponents);
                }
            }

            return actions.ToArray();
        }

        private void CreateCores()
        {

        }

        private void CreateComponents()
        {

        }
    }
}
